/**
 * @file rtk_semaphore.c
 * @author LokLiang (lokliang@163.com)
 * @brief
 * @version 0.1
 * @date 2023-05-01
 *
 * @copyright Copyright (c) 2023
 *
 */

#include "prv_rtk_class.h"

#include <stdarg.h>

rtk_err_t rtk_sem_create(rtk_sem_t *sem_handle, const char *name, size_t init_value, size_t max_value, rtk_sem_type type)
{
    _RTK_TRACE_ENTRY();
    _RTK_CHECK_ISR();
    _RTK_ASS_FALSE(sem_handle == NULL, "");
    _RTK_ASS_FALSE(max_value == 0, "");
    _RTK_ASS_FALSE(init_value > max_value, "");
    _RTK_ASS_FALSE(type != RTK_SEM_TYPE_FIFO && type != RTK_SEM_TYPE_PRIOR, "");

    rtk_err_t ret;

    do
    {
        struct rtk_sem_handle *sem = _rtk_name_malloc(name, sizeof(*sem));
        if (sem == NULL)
        {
            sem_handle->hdl = NULL;
            ret = RTK_NOMEM;
        }

        _rtk_sem_init(sem, init_value, max_value, type);
        sem_handle->hdl = sem;

        ret = RTK_OK;
    } while (0);

    _RTK_TRACE_EXIT();
    return ret;
}

rtk_err_t rtk_sem_delete(rtk_sem_t *sem_handle)
{
    _RTK_TRACE_ENTRY();
    _RTK_CHECK_ISR();
    _RTK_CHECK_HANDLE_ERR(sem_handle);

    struct rtk_sem_handle *sem = sem_handle->hdl;

    for (;;)
    {
        size_t sched_nest = rtk_sched_suspend();

        dlist_node_t *sig_node = dlist_take_head(&sem->sig_list);
        if (sig_node)
        {
            struct rtk_thread_handle *wait_thread = _RTK_CONTAINER_OF(sig_node, struct rtk_thread_handle, sig_node);
            _rtk_list_atomic_resume(wait_thread);

            rtk_sched_resume(sched_nest);
        }
        else
        {
            rtk_heapk_free(sem);
            sem_handle->hdl = NULL;

            rtk_sched_resume(sched_nest);

            _RTK_TRACE_EXIT();
            return RTK_OK;
        }
    }
}

rtk_err_t rtk_sem_clr(rtk_sem_t *sem_handle)
{
    _RTK_TRACE_ENTRY();
    _RTK_CHECK_ISR();
    _RTK_CHECK_HANDLE_ERR(sem_handle);

    struct rtk_sem_handle *sem = sem_handle->hdl;
    sem->value = 0;

    _RTK_TRACE_EXIT();
    return RTK_OK;
}

rtk_err_t rtk_sem_release(rtk_sem_t *sem_handle)
{
    _RTK_TRACE_ENTRY();
    _RTK_CHECK_HANDLE_ERR(sem_handle);

    struct rtk_sem_handle *sem = sem_handle->hdl;
    rtk_err_t ret = _rtk_sem_release(sem);

    _RTK_TRACE_EXIT();
    return ret;
}

rtk_err_t rtk_sem_take(rtk_sem_t *sem_handle, rtk_tick_t wait_ticks)
{
    _RTK_TRACE_ENTRY();
    _RTK_CHECK_HANDLE_ERR(sem_handle);
    _RTK_CHECK_SWAP(wait_ticks);

    struct rtk_sem_handle *sem = sem_handle->hdl;
    rtk_err_t ret = _rtk_sem_take(sem, wait_ticks);

    _RTK_TRACE_EXIT();
    return ret;
}

rtk_err_t rtk_sem_set_value(rtk_sem_t *sem_handle, size_t new_value)
{
    _RTK_TRACE_ENTRY();
    _RTK_CHECK_HANDLE_ERR(sem_handle);

    struct rtk_sem_handle *sem = sem_handle->hdl;
    _rtk_sem_set_value(sem, new_value);

    _RTK_TRACE_EXIT();
    return RTK_OK;
}

size_t rtk_sem_get_value(rtk_sem_t *sem_handle)
{
    _RTK_TRACE_ENTRY();
    struct rtk_sem_handle *sem = sem_handle->hdl;
    _RTK_TRACE_EXIT();
    return sem->value;
}

void *rtk_sem_peek_waiting(rtk_sem_t *sem_handle)
{
    _RTK_TRACE_ENTRY();
    struct rtk_sem_handle *sem = sem_handle->hdl;
    dlist_node_t *sig_node = dlist_peek_head(&sem->sig_list);
    void *ret;

    if (sig_node)
    {
        ret = _RTK_CONTAINER_OF(sig_node, struct rtk_thread_handle, sig_node);
    }
    else
    {
        ret = NULL;
    }

    _RTK_TRACE_EXIT();
    return ret;
}

bool rtk_sem_is_valid(rtk_sem_t *sem_handle)
{
    _RTK_TRACE_ENTRY();
    bool ret = _rtk_service_handle_is_valid(sem_handle);
    _RTK_TRACE_EXIT();
    return ret;
}

void _rtk_sem_init(struct rtk_sem_handle *sem, size_t init_value, size_t max_value, rtk_sem_type type)
{
    dlist_init_list(&sem->sig_list);
    sem->value = init_value;
    sem->max = max_value;
    sem->type = type;
    sem->wait_retry = 0;
}

void *_rtk_wait_memory(void *(*fn)(void *func_handle, va_list arp), void *func_handle, struct rtk_sem_handle *sem, rtk_tick_t wait_ticks, ...)
{
    va_list arp;
    va_start(arp, wait_ticks);
    void *ret = fn(func_handle, arp);
    va_end(arp);

    if (ret == NULL && rtk_scheduler_state() == RTK_SCHED_STATE_RUNNING && !rtk_is_isr_context())
    {
        struct rtk_thread_handle *self_thread = curr_thread_handle;
        int wakeup_tick = 0;
        if (wait_ticks == RTK_WAIT_FOREVER)
        {
            self_thread->wait_forever = 1;
        }
        else
        {
            _RTK_TICK_LIMIT(wait_ticks);
            wakeup_tick = cm_rtk->systime_tick + wait_ticks;
            self_thread->wait_forever = 0;
        }

        if (wait_ticks)
        {
            while (self_thread->wait_forever || (int)(wakeup_tick - *(int *)&cm_rtk->systime_tick) > 0)
            {
                _rtk_sem_set_value(sem, 0);
                _rtk_sem_take(sem, wait_ticks);

                va_start(arp, wait_ticks);
                ret = fn(func_handle, arp);
                va_end(arp);

                if (ret)
                {
                    _rtk_sem_release(sem);
                    break;
                }

                if (sem->wait_retry)
                {
                    wait_ticks = wakeup_tick - *(volatile int *)&cm_rtk->systime_tick;
                }
                else
                {
                    break;
                }
            }
        }
    }

    return ret;
}

void _rtk_sem_set_value(struct rtk_sem_handle *sem, size_t new_value)
{
    size_t nest_int = rtk_cpu_interrupt_save();
    if (new_value > sem->max)
    {
        new_value = sem->max;
    }
    sem->value = new_value;
    sem->wait_retry = !!new_value;
    rtk_cpu_interrupt_restore(nest_int);
}

rtk_err_t _rtk_sem_release(struct rtk_sem_handle *sem)
{
    size_t nest_int = rtk_cpu_interrupt_save();
    rtk_err_t ret;

    sem->wait_retry = 1;

    if (sem->value == 0)
    {
        struct rtk_thread_handle *thread = rtk_sem_peek_waiting((rtk_sem_t *)&sem);
        if (thread && (thread->flag & _RTK_THREAD_FLAG_SEM) == 0)
        {
            thread->flag |= _RTK_THREAD_FLAG_SEM;
            rtk_cpu_interrupt_restore(nest_int);

            _rtk_list_atomic_pend(thread);

            return RTK_OK;
        }
    }

    if (sem->value < sem->max)
    {
        ++sem->value;
        ret = RTK_OK;
    }
    else
    {
        ret = RTK_FAIL;
    }

    rtk_cpu_interrupt_restore(nest_int);

    return ret;
}

rtk_err_t _rtk_sem_take(struct rtk_sem_handle *sem, rtk_tick_t wait_ticks)
{
    size_t nest_int = rtk_cpu_interrupt_save();

    if (sem->value)
    {
        --sem->value;
        rtk_cpu_interrupt_restore(nest_int);
        return RTK_OK;
    }
    else if (wait_ticks == 0)
    {
        rtk_cpu_interrupt_restore(nest_int);
        return RTK_TIMEOUT;
    }
    else
    {
        struct rtk_thread_handle *self_thread = curr_thread_handle;
        self_thread->flag &= ~_RTK_THREAD_FLAG_SEM;
        rtk_cpu_interrupt_restore(nest_int);

        _DO_RTK_CHECK_ISR(); // 中断不允许延时

        if (wait_ticks == RTK_WAIT_FOREVER)
        {
            self_thread->wait_forever = 1;
        }
        else
        {
            self_thread->wait_forever = 0;
        }
        _RTK_TICK_LIMIT(wait_ticks);
        self_thread->wakeup_tick = cm_rtk->systime_tick + wait_ticks;
        self_thread->sem = sem;

        for (;;)
        {
            if (self_thread->flag & _RTK_THREAD_FLAG_SEM)
            {
                return RTK_OK;
            }
            else if ((int)(*(volatile int *)&self_thread->wakeup_tick - *(volatile int *)&cm_rtk->systime_tick) > 0)
            {
                _rtk_list_atomic_wait_sem(self_thread);
            }
            else
            {
                return RTK_TIMEOUT;
            }
        }
    }
}
